/* BIR with Visitor Pattern */
using System;
using System.Collections.Generic;
using opt;
using opt.Analysis.Branch;
using CLELCore;
using CLELCore.Assembly.CLELInstructions;
using CLELCore.Assembly.Descriptors;
using CLELCore.Assembly.Descriptors.ClassDescriptors;
using CLELCore.Assembly.MethodHeaderDataSections;
using CLELCore.Assembly.MethodHeaderDataSections.MethodHeaderDataSectionRows;

namespace opt.lib.BIR
{
	public class BIR : IOptimization
	{
		private BranchOver _br_over;

		//methods exception handling information
		private bool _is_fat;
		private byte _flags;
		private int _section_size;
		private List<Int32> _seh_flags;
		private List<Int32> _seh_try_from;
		private List<Int32> _seh_try_to;
		private List<Int32> _seh_handler_from;
		private List<Int32> _seh_handler_to;
		private List<Int32> _seh_tokenoroffset;

		public String Name
		{
			get
			{
				return "Branch Instruction Replacement";
			}
		}

		public BIR()
		{
		}
		
		private CLELInstruction DoReset(BranchKey key)
		{
			Int32 new_offset = _br_over.GetOffset(key);
			byte opcode = _br_over.GetOpcode(key);
			if(opcode == CLELOpcode.BR)
			{
				return new Br(new_offset);
			}
			else if(opcode == CLELOpcode.BRFALSE)
			{
				return new Brfalse(new_offset);
			}
			else if(opcode == CLELOpcode.BRTRUE)
			{
				return new Brtrue(new_offset);
			}
			else if(opcode == CLELOpcode.BEQ)
			{
				return new Beq(new_offset);
			}
			else if(opcode == CLELOpcode.BGE)
			{
				return new Bge(new_offset);
			}
			else if(opcode == CLELOpcode.BGT)
			{
				return new Bgt(new_offset);
			}
			else if(opcode == CLELOpcode.BLE)
			{
				return new Ble(new_offset);
			}
			else if(opcode == CLELOpcode.BLT)
			{
				return new Blt(new_offset);
			}
			else if(opcode == CLELOpcode.BNEUN)
			{
				return new Bneun(new_offset);
			}
			else if(opcode == CLELOpcode.BGEUN)
			{
				return new Bgeun(new_offset);
			}
			else if(opcode == CLELOpcode.BGTUN)
			{
				return new Bgtun(new_offset);
			}
			else if(opcode == CLELOpcode.BLEUN)
			{
				return new Bleun(new_offset);
			}
			else if(opcode == CLELOpcode.BLTUN)
			{
				return new Bltun(new_offset);
			}
			else if(opcode == CLELOpcode.LEAVE)
			{
				return new Leave(new_offset);
			}
			else if(opcode == CLELOpcode.BRS)
			{
				return new Brs((sbyte)new_offset);
			}
			else if(opcode == CLELOpcode.BRFALSES)
			{
				return new Brfalses((sbyte)new_offset);
			}
			else if(opcode == CLELOpcode.BRTRUES)
			{
				return new Brtrues((sbyte)new_offset);
			}
			else if(opcode == CLELOpcode.BEQS)
			{
				return new Beqs((sbyte)new_offset);
			}
			else if(opcode == CLELOpcode.BGES)
			{
				return new Bges((sbyte)new_offset);
			}
			else if(opcode == CLELOpcode.BGTS)
			{
				return new Bgts((sbyte)new_offset);
			}
			else if(opcode == CLELOpcode.BLES)
			{
				return new Bles((sbyte)new_offset);
			}
			else if(opcode == CLELOpcode.BLTS)
			{
				return new Blts((sbyte)new_offset);
			}
			else if(opcode == CLELOpcode.BNEUNS)
			{
				return new Bneuns((sbyte)new_offset);
			}
			else if(opcode == CLELOpcode.BGEUNS)
			{
				return new Bgeuns((sbyte)new_offset);
			}
			else if(opcode == CLELOpcode.BGTUNS)
			{
				return new Bgtuns((sbyte)new_offset);
			}
			else if(opcode == CLELOpcode.BLEUNS)
			{
				return new Bleuns((sbyte)new_offset);
			}
			else if(opcode == CLELOpcode.BLTUNS)
			{
				return new Bltuns((sbyte)new_offset);
			}
			else if(opcode == CLELOpcode.LEAVES)
			{
				return new Leaves((sbyte)new_offset);
			}
			else //SWITCH
			{
				List<Int32> off = _br_over.GetSwitchOffsets(key);
				return new Switch(off);
			}
		}

		private CLELInstruction DoReplace(BranchKey key)
		{
			Int32 offset = _br_over.GetOffset(key);
			byte opcode = _br_over.GetOpcode(key);
			sbyte new_offset = (sbyte)offset;
			if(opcode == CLELOpcode.BR)
			{
				return new Brs(new_offset);
			}
			else if(opcode == CLELOpcode.BRFALSE)
			{
				return new Brfalses(new_offset);
			}
			else if(opcode == CLELOpcode.BRTRUE)
			{
				return new Brtrues(new_offset);
			}
			else if(opcode == CLELOpcode.BEQ)
			{
				return new Beqs(new_offset);
			}
			else if(opcode == CLELOpcode.BGE)
			{
				return new Bges(new_offset);
			}
			else if(opcode == CLELOpcode.BGT)
			{
				return new Bgts(new_offset);
			}
			else if(opcode == CLELOpcode.BLE)
			{
				return new Bles(new_offset);
			}
			else if(opcode == CLELOpcode.BLT)
			{
				return new Blts(new_offset);
			}
			else if(opcode == CLELOpcode.BNEUN)
			{
				return new Bneuns(new_offset);
			}
			else if(opcode == CLELOpcode.BGEUN)
			{
				return new Bgeuns(new_offset);
			}
			else if(opcode == CLELOpcode.BGTUN)
			{
				return new Bgtuns(new_offset);
			}
			else if(opcode == CLELOpcode.BLEUN)
			{
				return new Bleuns(new_offset);
			}
			else if(opcode == CLELOpcode.BLTUN)
			{
				return new Bltuns(new_offset);
			}
			else //CLELOpcode.LEAVE
			{
				return new Leaves(new_offset);
			}
		}

		private void LoadSEH(List<CLELInstruction> code,MethodHeaderDataSection seh)
		{
			if(seh != null)
			{
				_is_fat = seh.IsFat;
				_flags = seh.Flags;
				_section_size = seh.SectionSize;
				_seh_flags = new List<Int32>();
				_seh_try_from = new List<Int32>();
				_seh_try_to = new List<Int32>();
				_seh_handler_from = new List<Int32>();
				_seh_handler_to = new List<Int32>();
				_seh_tokenoroffset = new List<Int32>();

				Dictionary<Int32,Int32> offsets = new Dictionary<Int32,Int32>();
				int i,count = code.Count;
				int off = 0;
				for(i = 0;i < count;i++)
				{
					offsets.Add(off,i);
					off += code[i].Length;
				}
				List<MethodHeaderDataSectionRow> rows = seh.Rows;
				count = rows.Count;
				int from_try,to_try,from_handler,to_handler;
				SEHDataSectionRow row;
				for(i = 0;i < count;i++)
				{
					row = (SEHDataSectionRow)rows[i];
					from_try = offsets[row.TryOffset];
					to_try = row.TryOffset+row.TryLength;
					to_try = offsets[to_try];

					from_handler = offsets[row.HandlerOffset];
					to_handler = row.HandlerOffset+row.HandlerLength;
					to_handler = offsets[to_handler];
					_seh_flags.Add(row.Flags);
					_seh_try_from.Add(from_try);
					_seh_try_to.Add(to_try);
					_seh_handler_from.Add(from_handler);
					_seh_handler_to.Add(to_handler);
					_seh_tokenoroffset.Add(row.TokenOrOffset);
				}
			}
			else
			{
				_seh_flags = null;
				_seh_try_from = null;
				_seh_try_to = null;
				_seh_handler_from = null;
				_seh_handler_to = null;
				_seh_tokenoroffset = null;
			}
		}
		
		private MethodHeaderDataSection ResetSEH(List<CLELInstruction> code)
		{
			if(_seh_flags != null)
			{
				Dictionary<Int32,Int32> offsets = new Dictionary<Int32,Int32>();
				int i,count = code.Count;
				int off = 0;
				for(i = 0;i < count;i++)
				{
					offsets.Add(i,off);
					off += code[i].Length;
				}
				count = _seh_try_from.Count;
				int flags,from_try,to_try,from_handler,to_handler,tokenoroffset;
				SEHDataSectionRow row;
				MethodHeaderDataSection sec = new MethodHeaderDataSection(_flags,_section_size);
				for(i = 0;i < count;i++)
				{
					flags = _seh_flags[i];
					tokenoroffset = _seh_tokenoroffset[i];
					from_try = _seh_try_from[i];
					to_try = _seh_try_to[i];
					from_handler = _seh_handler_from[i];
					to_handler = _seh_handler_to[i];

					from_try = offsets[from_try];
					to_try = offsets[to_try] - from_try;
					from_handler = offsets[from_handler];
					to_handler = offsets[to_handler] - from_handler;

					row = new SEHDataSectionRow(flags,from_try,to_try,from_handler,to_handler,tokenoroffset,_is_fat);
					sec.AddRow(row);
				}
				return sec;
			}
			return null;
		}

		public void DoOptimization(CLEL clel,CLELLogger log)
		{
			log.WriteLine("BIR: " + clel.Path+"\n");

			List<ClassDescriptor> class_descriptors = clel.GetInternalClasses();
			List<MethodDescriptor> method_descriptors;
			int i,class_count = class_descriptors.Count;
			int j,method_count;
			int k,keys_count;
			ClassDescriptor cd;
			TypeDefClassDescriptor tycd;
			MethodDescriptor md;
			List<CLELInstruction> code;
			IBIRMethod bir_method;
			List<BranchKey> keys;
			BranchKey key;
			bool[] was_replaced;
			bool changed;
			//for each class
			for(i = 0;i < class_count;i++)
			{
				cd = class_descriptors[i];
				if(cd.Type == ClassDescriptor.TYPEDEF_CLASSDESCRIPTOR)
				{
					tycd = (TypeDefClassDescriptor)cd;
					method_descriptors = tycd.Methods;
					method_count = method_descriptors.Count;
					//for each method
					for(j = 0;j < method_count;j++)
					{
						md = method_descriptors[j];
						code = md.GetMethodsCode();
						changed = false;
						if(code != null)
						{
							MethodHeaderDataSection seh = md.SEHDataSection;
							LoadSEH(code,seh);
							_br_over = new BranchOver(code);
							keys = _br_over.GetKeys();
							keys_count = keys.Count;
							was_replaced = new bool[keys_count];
							for(k = 0;k < keys_count;k++)
								was_replaced[k] = false;

							bir_method = new PeepholeBIRMethod(keys);
							changed = bir_method.Accept(new BIRVisitor(_br_over,was_replaced));

							//if any branch was changed
							//update code
							if(changed)
							{
								for(k = 0;k < keys_count;k++)
								{
									key = keys[k];
									if(was_replaced[k])
										code[key.Index] = DoReplace(key);
									else
										code[key.Index] = DoReset(key);
								}
								md.SEHDataSection = ResetSEH(code);
								//set code
								md.SetMethodsCode(code,null);
							}
						}
					}
				}
			}
		}
	}
}
